package edu.asu.sapa.ground;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import edu.asu.sapa.Planner;
import edu.asu.sapa.ground.update.*;
import edu.asu.sapa.lifted.Action;
import edu.asu.sapa.lifted.Constant;
import edu.asu.sapa.lifted.Function;
import edu.asu.sapa.lifted.LiftedGoal;
import edu.asu.sapa.lifted.LiftedSet;
import edu.asu.sapa.lifted.LiftedTest;
import edu.asu.sapa.lifted.LiftedMathForm;
import edu.asu.sapa.lifted.Predicate;
import edu.asu.sapa.lifted.LiftedProblem;
import edu.asu.sapa.lifted.Symbol;
import edu.asu.sapa.lifted.SymbolTable;
import edu.asu.sapa.lifted.Variable;
import edu.asu.sapa.utils.Int;


public class Problem {
	public LiftedProblem lifted;
	boolean analysisFlag = false;

	//public SymbolTable<Integer, Goal> goals = new SymbolTable<Integer, Goal>();
	public State init;

	public static SymbolTable<ArrayList<Integer>, Operator> operatorTable = new SymbolTable<ArrayList<Integer>, Operator>();
	public static SymbolTable<ArrayList<Integer>, Proposition> propositionTable = new SymbolTable<ArrayList<Integer>, Proposition>();
	public static SymbolTable<ArrayList<Integer>, Fluent> fluentTable = new SymbolTable<ArrayList<Integer>, Fluent>();
	public static ArrayList<Operator> operators = operatorTable.symbols;
	public static ArrayList<Fluent> fluents = fluentTable.symbols;
	public static ArrayList<Proposition> propositions = propositionTable.symbols;
	
	
	public static final HashMap<LiftedGoal,Proposition> goalToProposition = new HashMap<LiftedGoal,Proposition>();
	public static final HashMap<String,Operator> actSigMap = new HashMap<String,Operator>();
	public static final HashMap<String,Proposition> predSigMap = new HashMap<String,Proposition>();
	
	public static final HashMap<String,ArrayList<Proposition>> goalDependencies = new HashMap<String,ArrayList<Proposition>>();
	
	static { 
		ArrayList<Integer> name = new ArrayList<Integer>();

		propositionTable.put(new Proposition(name,true));
		fluentTable.put(new Fluent(name));
		operatorTable.put(new Operator(name));
	}
	

	transient Constant[] map;

	// for updates
	transient int oldOperatorCount=0;
	transient public boolean addedReachableOperator=false;
	transient public boolean removedReachableOperator=false;
	transient public int numReachableOperators; 
	transient public int numReachableProps; 
	transient public int numReachableFluents; 


	public Problem(boolean analysisFlag) {
		this.analysisFlag = analysisFlag;
	}

	public Problem(Problem g) {
		analysisFlag = g.analysisFlag;

		//goals = new SymbolTable<Integer, Goal>(g.goals);
		init = new State(g.init);

		operatorTable = new SymbolTable<ArrayList<Integer>, Operator>(Problem.operatorTable);
		propositionTable = new SymbolTable<ArrayList<Integer>, Proposition>(Problem.propositionTable);
		fluentTable = new SymbolTable<ArrayList<Integer>, Fluent>(Problem.fluentTable);

		//prob = null;
	}

	private void analyzeStatic() {
		int numProp = propositionTable.count;
		ArrayList<Proposition> props = propositionTable.symbols;

		numReachableOperators = 0;
		numReachableProps = 0;
		numReachableFluents = 0;

		boolean wasPossible;

		int i=1;
		
		for (; i < oldOperatorCount; i++) {
			Operator instance = operatorTable.get(i);
			if (instance == Operator.nullOperator) { 
				removedReachableOperator = true;
				continue;
			}
			wasPossible = instance.isPossible;
			instance.analyzeStatic();
			if (instance.isPossible) {
				++numReachableOperators;
				instance.setDependencies();
				if (!wasPossible)
					addedReachableOperator = true;
			} else {
				instance.removeDependencies();
				if (wasPossible)
					removedReachableOperator = true;
			}
		}


		for (; i < operatorTable.count; i++) {
			Operator instance = operatorTable.get(i);
			instance.analyzeStatic();
			if (instance.isPossible) {
				instance.setDependencies();
				++numReachableOperators;
				addedReachableOperator = true;
			} else {
				instance.removeDependencies();
			}
		}

		init.analyzeStatic();
		
		for (i = 1; i < numProp; ++i) {
			Proposition p = props.get(i);
			// saves memory, but takes time
			//p.trim();
			if (p.isConstant) {
				init.propDB.remove(p.id);
			} else {
				++numReachableProps;
			}
		}

		for (i = 1; i < fluentTable.count; ++i) {
			Fluent f = fluentTable.symbols.get(i);
			if (f.isConstant) {
				init.fluentDB.remove(f.id);
			} else {
				++numReachableFluents;
			}
		}
		if (numReachableProps == 0)
			init.propDB = null;
		if (numReachableFluents == 0)
			init.fluentDB = null;

	}

	public Fluent getFluent(int index) {
		return fluentTable.get(index);
	}

	public Operator getOperator(int index) {
		return operatorTable.get(index);
	}

	public ArrayList<Operator> getOperators() {
		return operatorTable.symbols;
	}

	public Proposition getProposition(int index) {
		return propositionTable.get(index);
	}

	public ArrayList<Proposition> getPropositions() {
		return propositionTable.symbols;
	}

	ArrayList<Update> startPoint = new ArrayList<Update>();
	ArrayList<Update> firstInterval = new ArrayList<Update>();
	ArrayList<Update> finishPoint = new ArrayList<Update>();
	pddlPropEffectDB pddlDB = new pddlPropEffectDB();


	private Operator groundAction(Action template, Operator instance) {
		int k;

		MathForm duration = groundMathForm(template.duration, Float.NaN);
		instance.setDuration(duration);
		float dur = duration.value();

		Update update;

		Proposition prop;
		MathForm time;

		int size;

		// not supporting soft conditions
		for (k=0, size = template.numCondition(); k < size; ++k) {
			LiftedGoal g = template.getCondition(k);
			prop = groundPredicate(g.predicate);
			time = groundMathForm(g.start,dur);

			pddlDB.condition(prop.id,true,time);
		}

		for (k = 0, size = template.numProtectCondition(); k < size; k++) {
			prop = groundPredicate(template.getProtectCondition(k));

			pddlDB.lock(prop.id,true);
		}

		for (k = 0, size = template.numAdd(); k < size; k++) {
			prop = groundPredicate(template.getAdd(k));
			time = groundMathForm(template.getAddTime(k), dur);
			
			pddlDB.add(prop.id,time);
		}

		for (k = 0, size = template.numDelete(); k < size; k++) {
			prop = groundPredicate(template.getDelete(k));
			time = groundMathForm(template.getDeleteTime(k), dur);

			pddlDB.delete(prop.id,time);
		}


		if (!pddlDB.makeAll(propositionTable,startPoint,firstInterval,finishPoint)) {
			startPoint.clear();
			finishPoint.clear();
			firstInterval.clear();
			pddlDB.clear();
			return null;
		}

		for (k = 0, size = template.numAdd(); k < size; k++) {
			prop = getPredicate(template.getAdd(k));
			if (prop.value == false) {
				prop.isConstant = false;
				this.updateDependents(prop.name.get(0),prop);
			}
		}

		for (k = 0, size = template.numDelete(); k < size; k++) {
			prop = getPredicate(template.getDelete(k));
			if (prop.value == true) {
				prop.isConstant = false;
			}
		}


		LiftedTest testTemplate;
		MathForm l, r;
		Fluent f;
		for (k = 0; k < template.numTest(); k++) {
			testTemplate = template.getTest(k);

			l = groundMathForm(testTemplate.getLeftSide(), dur);
			r = groundMathForm(testTemplate.getRightSide(), dur);
			time = groundMathForm(template.getTestTime(k), dur);

			update = new Test(testTemplate.op, l, r);
			if (time == MathForm.StartRef) {
				startPoint.add(update);
			} else if (time == MathForm.EndRef) {
				finishPoint.add(update);
			} else {
				System.err.println("Too lazy to implement delayed updates inside actions.");
			}
		}

		LiftedSet setTemplate;
		for (k = 0; k < template.numSet(); k++) {
			setTemplate = template.getSet(k);

			r = groundMathForm(setTemplate.getRightSide(), dur);
			time = groundMathForm(template.getSetTime(k), dur);
			f = groundFluent(setTemplate.getLeftSide());
			
			float u = f.value;
			if (setTemplate.getOp() == 0) {
				float v = r.value();
				if (v != v) {
					if (f.isConstant) {
						f.isConstant = false;
						if (u != u)
							updateDependents(f.name.get(0),f);
					}
				} else {
					if (u == u) {
						if (u != v) {
							f.isConstant = false;
						}
					} else {
						if (f.isConstant) {
							f.isConstant = false;
							updateDependents(f.name.get(0),f);
						}
					}
				}
			} else {
				if (u == u)
					f.isConstant = false;
			}

			update = new Set(setTemplate.getOp(), f.id, r);
			if (time == MathForm.StartRef) {
				startPoint.add(update);
			} else if (time == MathForm.EndRef) {
				finishPoint.add(update);
			} else {
				System.err.println("Too lazy to implement delayed updates inside actions.");
			}
		}

		int loaders = 0;
		if (startPoint.size() > 0)
			++loaders;
		if (firstInterval.size() > 0)
			++loaders;
		if (finishPoint.size() > 0)
			++loaders;
		instance.contents = new Load[loaders];
		loaders=0;
		if (startPoint.size() > 0)
			instance.contents[loaders++] = new StartPointLoader(startPoint.toArray(ArrayListUpdate_to_UpdateArray));
		if (firstInterval.size() > 0)
			instance.contents[loaders++] = new FirstIntervalLoader(firstInterval.toArray(ArrayListUpdate_to_UpdateArray));
		if (finishPoint.size() > 0)
			instance.contents[loaders++] = new EndPointLoader(finishPoint.toArray(ArrayListUpdate_to_UpdateArray));

		startPoint.clear();
		finishPoint.clear();
		firstInterval.clear();
		pddlDB.clear();
		
		instance.setCost(groundMathForm(template.cost, dur));

		++numReachableOperators;
		
		// for easy access when dealing with separate SAS operators
		actSigMap.put(instance.getName(),instance);
		
		return instance;
	}
	
	static final Update[] ArrayListUpdate_to_UpdateArray = new Update[0];

	private Fluent getFluent(Function template) {
		return fluentTable.get(template.bind(map));
	}

	private Fluent groundFluent(Function template) {
		Fluent p = new Fluent(template.bind(map));
		Fluent q = fluentTable.put(p);

		return q;
	}

	@SuppressWarnings("unused")
	@Deprecated
	private int groundFunction(Function template, float value) {
		Fluent f = new Fluent(template.bind(map));
		Fluent q = fluentTable.put(f);
		if (q == f) {
			// addFEvent(template.id, f);
			updateDependents(template.id, q);
			q.value = value;
			if (value != value) {
				q.isConstant = true;				
			} else
				q.isConstant = false;
		} else if (q.value != value && (value == value || q.value == q.value) && q.isConstant) {
			q.value = value;
			q.isConstant = false;
		}
		return q.id;
	}

	private MathForm groundMathForm(LiftedMathForm template, float dur) {
		switch (template.type) {
		case MathForm.Constant:
			if (template.value == MathForm.Zero.value)
				return MathForm.Zero;
			if (template.value == MathForm.One.value)
				return MathForm.One;
			if (template.value == MathForm.Epsilon.value)
				return MathForm.Epsilon;
			if (template.value == MathForm.TwoEpsilon.value)
				return MathForm.TwoEpsilon;
			if (template.value == MathForm.PInf.value)
				return MathForm.PInf;
			if (template.value == MathForm.NInf.value)
				return MathForm.NInf;
			if (template.value != template.value)
				return MathForm.NaN;

			return new MathForm(template.value);
		case MathForm.Duration:
			if (dur == dur)
				return new MathForm(dur);
			return MathForm.newDurRef();
		case MathForm.Start:
			return MathForm.StartRef;
		case MathForm.End:
			return MathForm.EndRef;
		case MathForm.Fluent:
			Fluent f = getFluent(template.fluent);
			if (f == null)
				return MathForm.NaN;
			return new MathForm(f);
		case MathForm.Operator:
			MathForm left = groundMathForm(template.left, dur);
			MathForm right = groundMathForm(template.right, dur);
			if ((left.type != 1) || (right.type != 1))
				return new MathForm(template.op, left, right);
			switch (template.op) {
			case '+':
				return new MathForm(left.value + right.value);
			case '-':
				return new MathForm(left.value - right.value);
			case '*':
				return new MathForm(left.value * right.value);
			case '/':
				return new MathForm(left.value / right.value);
			}
			break;
		case MathForm.DT:
			//			return GMathForm.NaN;
		default:
			break;
		}
		throw new NullPointerException(template.toString());
	}

	private Proposition getPredicate(Predicate template) {
		return propositionTable.get(template.bind(map));
	}

	private Proposition groundPredicate(Predicate template) {
		Proposition q = new Proposition(template.bind(map));
		Proposition p = propositionTable.put(q);

		predSigMap.put(p.getName(), p);
		
		return p;
	}

	@SuppressWarnings("unused")
	@Deprecated
	private int groundPredicate(Predicate template, boolean v) {
		Proposition p = new Proposition(template.bind(map));
		Proposition q = propositionTable.put(p);
		if (q == p) {
			// addPEvent(template.id, p);
			updateDependents(template.id, q);
			q.value = v;
			if (!v)
				q.isConstant = true;
			else
				q.isConstant = false;
		} else if (q.value != v && q.isConstant) {
			q.isConstant = false;
			q.value = v;
		}
		// propositions.put(p);
		//
		// while (reachable.size() <= p.id) {
		// reachable.add(false);
		// }
		// if (add && !reachable.get(p.id)) {
		// addPEvent(template.id, p);
		// reachable.set(p.id, true);
		// }
		return q.id;
	}

	private void updateDependents(int id, Proposition p) {
		Predicate template = lifted.predicates.get(id);
		for (int i = 0, size = p.name.size() - 1; i < size; ++i) {
			HashSet<Constant> objs = template.reachable.get(i);
			Constant c = lifted.constants.get(p.name.get(i + 1));
			if (!objs.contains(c)) {
				objs.add(c);
				for (Predicate d : template.dependents) {
					Symbol<String> s = d.get(i);
					if (s instanceof Variable) {
						Variable v = (Variable) s;
						Int count = v.unreachable.get(c);
						// c may not be of v's type
						if (count == null)
							continue;
						if (--count.value == 0) {
							v.reachable.add(c);
						}
					}
				}
			}
		}

	}

	private void updateDependents(int id, Fluent f) {
		Function template = lifted.functions.get(id);
		for (int i = 0, size = f.name.size() - 1; i < size; ++i) {
			HashSet<Constant> objs = template.reachable.get(i);
			Constant c = lifted.constants.get(f.name.get(i + 1));
			if (!objs.contains(c)) {
				objs.add(c);
				for (Function d : template.dependents) {
					Symbol<String> s = d.get(i);
					if (s instanceof Variable) {
						Variable v = (Variable) s;
						Int count = v.unreachable.get(c);
						if (count == null)
							continue;
						if (--count.value == 0) {
							v.reachable.add(c);
						}
					}
				}
			}
		}
	}

	private void updateTemplate(Action template, int index) {
		if (index == map.length) {
			ArrayList<Integer> name = template.bind(map);
			Operator instance = operatorTable.get(name);
			if (instance != null) {
				int i = instance.id;
				if (operatorTable.symbols.get(i) != Operator.nullOperator)
					return;
				if (preGroundAction(template,instance)) {
					instance = groundAction(template, instance); 
					if (instance != null) {
						operatorTable.symbols.set(i,instance);
					}
				}
				return;
			}

			instance = new Operator(name);
			if (preGroundAction(template, instance)) {
				instance = groundAction(template, instance); 
				if (instance != null) {
					operatorTable.put(instance);
				}
			}
		} else {
			Variable v = template.order[index++];
			//			for (Constant o : v.type.constants) {
			//			map[v.id] = o;
			for (int i = v.reachable.size() - 1; i >= 0; --i) {
				map[v.id] = v.reachable.get(i);
				// for (Constant object : v.reachable) {
				// map[v.id] = object;
				updateTemplate(template, index);
			}
		}
	}



	private ArrayList<Operator> groundTemplates() {
		int count;
		int size = lifted.actions.count;
		ArrayList<Action> templates = lifted.actions.symbols;
		numReachableOperators=0;
		// ugly hack to track which actions have become re-reachable
		for(int i = operatorTable.count-1; i>=1; --i)
			operatorTable.symbols.set(i,Operator.nullOperator);
		do {
			count = numReachableOperators;
			for (int i = 1; i < size; ++i) {
				Action template = templates.get(i);
				// for (int j = 0; j < pEventsSize; ++j) {
				// updateDependents(pEvents[j].id, pEvents[j].p);
				// }
				// pEventsSize = 0;
				// for (int j = 0; j < fEventsSize; ++j) {
				// updateDependents(fEvents[j].id, fEvents[j].f);
				// }
				// fEventsSize = 0;
				map = template.map;
				updateTemplate(template, 0);
			}
		} while (count != numReachableOperators);

		return operatorTable.symbols;
	}

	public float initialize(LiftedProblem prob) {

		this.lifted = prob;
		prob.initGrounding();

		float maxReward = groundInit(prob.initAction);

		groundTemplates();

		analyzeStatic();

		return maxReward;
	}


	public float update() {
		lifted.updateGrounding();
		
		oldOperatorCount = operatorTable.count;
		addedReachableOperator = false;
		removedReachableOperator = false;

		float maxReward = groundInit(lifted.initAction);

		groundTemplates();

		analyzeStatic();

		return maxReward;
	}

	// can bypass actually making a loadable action.
	// all times should be constants (or constant expressions), because the description is taking place in the -inf world, 
	// where everything is undefined/NaN, so no expressions work besides constants.
	// w.r.t. updates one could argue that there is a defined state that the update is taking place in (the prior problem)
	// but, for simplicity, let's not support that particular possibility.
	// given constant times we can ground by directly loading into the event queue.
	private float groundInit(Action template) {
		int k;
		float maxReward = 0f;

		float start = Float.NEGATIVE_INFINITY;
		float duration = Float.POSITIVE_INFINITY;
		float end = Float.POSITIVE_INFINITY;
		Context c = new Context(0,start,duration,end);
		init = new State(c);
		init.propDB.setAtInstant(0,true,start);
		
		float now = lifted.now.value;

		maxReward -= groundMathForm(template.cost,duration).value(init,c);

		Update u;
		LiftedTest testTemplate;
		for (k = 0; k < template.numTest(); k++) {
			testTemplate = template.getTest(k);

			MathForm l = groundMathForm(testTemplate.getLeftSide(), duration);
			MathForm r = groundMathForm(testTemplate.getRightSide(), duration);
			MathForm t = groundMathForm(template.getTestTime(k), duration);

			u = new Test(testTemplate.op,l,r);
			init.events.add(c,u,UpdateWrapper.UpdateBefore,t.value(init,c));
		}

		LiftedSet setTemplate;
		for (k = 0; k < template.numSet(); k++) {
			setTemplate = template.getSet(k);

			MathForm r = groundMathForm(setTemplate.getRightSide(), duration);
			MathForm t = groundMathForm(template.getSetTime(k), duration);

			Fluent f;
			f = groundFluent(setTemplate.getLeftSide());
			f.isConstant = false; // to make sure rolling init state forward succeeds on deadline goals

			u = new Set(setTemplate.getOp(), f.id, r);
			init.events.add(c,u,UpdateWrapper.UpdateBefore,t.value(init,c));
		}

		Proposition prop;
		MathForm time;
		float reward, penalty;

		int size;

		k=0;
		float t,e;
		for (LiftedGoal g : template.conditions) {
			prop = groundPredicate(g.predicate);

			goalToProposition.put(g, prop);

			ArrayList<Proposition> dep;
			if ((dep = goalDependencies.get(g.label)) == null) {
				dep = new ArrayList<Proposition>();
				goalDependencies.put(g.label, dep);
			}
			dep.add(prop);

			reward = g.reward;
			penalty = g.penalty;

			t = groundMathForm(g.start,duration).value(init,c);
			e = groundMathForm(g.finish,duration).value(init,c);

			maxReward += Math.max(reward,-penalty);

			if (penalty < Float.POSITIVE_INFINITY) {
				u = new SoftCondition(prop, prop.id, true, reward, penalty);						
			} else if (reward != 0) {
				u = new SoftCondition(prop, prop.id, true, reward, penalty);						
			} else {
				u = new Condition(prop, prop.id, true);			
			}
			if (t == e)
				init.events.add(c,u,UpdateWrapper.UpdateBefore,t);
			else {
				init.events.add(c,u,UpdateWrapper.StartBefore,t);
				init.events.add(c,u,UpdateWrapper.FinishBefore,e);
			}
		}

		for (k = 0, size = template.numProtectCondition(); k < size; k++) {
			prop = groundPredicate(template.getProtectCondition(k));
			u = new Condition(prop, prop.id, true); 
			init.events.add(c,u,UpdateWrapper.StartAfter,now);
		}

//		int index = 0;
		for (k = 0, size = template.numAdd(); k < size; k++) {
			prop = groundPredicate(template.getAdd(k));
			time = groundMathForm(template.getAddTime(k), duration);

			prop.isConstant = false;

			u = new Assign(prop, prop.id, true);
			init.events.add(c,u,UpdateWrapper.UpdateBefore,time.value(init,c));

		}

		for (k = 0, size = template.numDelete(); k < size; k++) {
			prop = groundPredicate(template.getDelete(k));
			time = groundMathForm(template.getDeleteTime(k), duration);

			prop.isConstant = false;

			u = new Assign(prop, prop.id, false);
			init.events.add(c,u,UpdateWrapper.UpdateBefore,time.value(init,c));
		}


		init.advance();
		init.advance(now+Planner.EPSILON);

		int i=1;
		for (; i < propositionTable.count; ++i) {
			Proposition p = propositionTable.get(i);
			boolean v = init.propDB.getValueBefore(p.id,now+Planner.EPSILON,true);
			p.isConstant = true;
			p.value = v; 
			if (v)
				updateDependents(p.name.get(0),p);
		}

		i=1;
		for (; i < fluentTable.count; ++i) {
			Fluent f = fluentTable.get(i);
			float v = init.fluentDB.value(f.id);
			f.isConstant = true;
			f.value = v;
			if (v == v)
				updateDependents(f.name.get(0),f);
		}


		Proposition p;
		for (UpdateWrapper<?> pr : init.events) {
			int id;
			boolean v;
			u = pr.event;
			id = 0;
			if (u instanceof Set) {
				Set s = (Set) u;
				Fluent f = fluentTable.symbols.get(s.id);
				float val = f.value;
				if (s.op != 0) {
					if (val == val)
						f.isConstant = false;
				} else {
					float val2 = s.rightSide.value(init,c);
					if (val2 != val2) {
						if (f.isConstant) {
							f.isConstant = false;
							if (val != val)
								updateDependents(f.name.get(0),f);
						}
					} else {
						if (val == val) {
							if (val != val2) {
								f.isConstant = false;
							}
						} else {
							if (f.isConstant) {
								f.isConstant = false;
								updateDependents(f.name.get(0),f);
							}
						}
					}
				}
				continue;
			} else if (u instanceof Assign) {
				id = ((Assign)u).id;
				v = ((Assign)u).value;
			} else if (u instanceof Change) {
				id = ((Change)u).id;
				v = ((Change)u).value;
			} else if (u instanceof ConditionStartAssign) {
				id = ((ConditionStartAssign)u).id;
				v = ((ConditionStartAssign)u).writeValue;
			} else if (u instanceof ConditionEndAssign) {
				id = ((ConditionEndAssign)u).id;
				v = ((ConditionEndAssign)u).writeValue;
			} else if (u instanceof ConditionChange) {
				id = ((ConditionChange)u).id;
				v = ((ConditionChange)u).writeValue;
			} else if (u instanceof TemporaryAssign) {
				p = propositionTable.symbols.get(((TemporaryAssign)u).id);
				p.isConstant = false;
				updateDependents(p.name.get(0),p);
				continue;
			} else if (u instanceof TemporaryConditionAssign) {
				p = propositionTable.symbols.get(((TemporaryConditionAssign)u).id);
				p.isConstant = false;
				updateDependents(p.name.get(0),p);
				continue;
			} else {
				continue;
			}
			p = propositionTable.symbols.get(id);
			if (p.value != v) {
				p.isConstant = false;
				if (v)
					updateDependents(p.name.get(0),p);
			}
		}

		return maxReward;
	}

	public float getDependencyReward(String name) {
		return lifted.prefReward.get(name);
	}
	
	public int numAct() {
		return operatorTable.count;
	}

	public int numFunc() {
		return fluentTable.count;
	}

	public int numProp() {
		return propositionTable.count;
	}

	private boolean preGroundAction(Action template, Operator instance) {

		if (!preGroundMathForm(template.duration, null))
			return false;

		if (!preGroundMathForm(template.cost, template.duration))
			return false;

		int k;
		int size;

//		Proposition prop;
//		MathForm time;
//		float reward, penalty;

		k=0;
		for (LiftedGoal g : template.conditions) {
			// TODO: handle passing in the value of the condition
			// TODO: or properly STRIP-ify the domain [add negated propositions, forall f, neg-f == (not f)]
			if (!preGroundPredicate(g.predicate))
				return false;
			if (!preGroundMathForm(g.start,template.duration))
				return false;
			if (g.reward == Float.NEGATIVE_INFINITY && g.penalty == Float.POSITIVE_INFINITY)
				return false;
			++k;
		}

		LiftedTest liftedTestTemplate;
		for (k = 0, size = template.numTest(); k < size; k++) {
			liftedTestTemplate = template.getTest(k);

			if (!preGroundMathForm(liftedTestTemplate.getLeftSide(),
					template.duration))
				return false;
			if (!preGroundMathForm(liftedTestTemplate.getRightSide(),
					template.duration))
				return false;
			if (!preGroundMathForm(template.getTestTime(k), template.duration))
				return false;
		}

		for (k = 0, size = template.numProtectCondition(); k < size; k++) {
			if (!preGroundPredicate(template.getProtectCondition(k)))
				return false;
		}

		for (k = 0, size = template.numProtectTest(); k < size; k++) {
			liftedTestTemplate = template.getProtectTest(k);
			if (!preGroundMathForm(liftedTestTemplate.getLeftSide(),
					template.duration))
				return false;
			if (!preGroundMathForm(liftedTestTemplate.getRightSide(),
					template.duration))
				return false;
			return false;
		}

		LiftedSet setTemplate;
		for (k = 0; k < template.numSet(); k++) {
			setTemplate = template.getSet(k);

			if (!preGroundMathForm(setTemplate.getRightSide(),
					template.duration))
				return false;
			if (!preGroundMathForm(template.getSetTime(k), template.duration))
				return false;
		}

		for (k = 0, size = template.numAdd(); k < size; k++) {
			if (!preGroundMathForm(template.getAddTime(k), template.duration))
				return false;
		}

		for (k = 0, size = template.numDelete(); k < size; k++) {
			if (!preGroundMathForm(template.getDeleteTime(k),
					template.duration))
				return false;
		}

		return true;
	}

	private boolean preGroundFunction(Function template) {
		Fluent tempF = fluentTable.get(template.bind(map));
		if (tempF == null || (tempF.isConstant && tempF.value != tempF.value))
			return false;
		return true;
	}

	private boolean preGroundMathForm(LiftedMathForm template, LiftedMathForm dur) {
		switch (template.type) {
		case MathForm.Duration:
			if (dur == null)
				return false;
			return true;
		case MathForm.Constant:
		case MathForm.Start:
		case MathForm.End:
			return true;
		case MathForm.Fluent:
			return preGroundFunction(template.fluent);
		case MathForm.Operator:
			if (!preGroundMathForm(template.left,dur))
				return false;
			if (!preGroundMathForm(template.right,dur))
				return false;
			switch (template.op) {
			case '+':
			case '-':
			case '*':
			case '/':
				return true;
			}
			return false;
		case MathForm.DT:
		default:
			return false;
		}
	}

	private boolean preGroundPredicate(Predicate template) {
		Proposition p = propositionTable.get(template.bind(map));
		if (p == null)
			return false;
		// only works for purely positive preconditions. Otherwise we need
		// to pass in the value of the condition.
		if (p.isConstant)
			return p.value;
		return true;
	}

	public boolean premerge(LiftedProblem pu) {
		return false;
	}

	public boolean pregoals(LiftedProblem pu) {
		return true;
	}

	public void updateGoals(LiftedProblem pu) {
		// TODO Auto-generated method stub
	}

	public int numDeps() {
		return lifted.preferenceNames.size();
	}

}



class PEvent {
	public int id;
	public Proposition p;

	public void set(int id, Proposition p) {
		this.id = id;
		this.p = p;
	}
}

class FEvent {
	public int id;
	public Fluent f;

	public void set(int id, Fluent f) {
		this.id = id;
		this.f = f;
	}
}

// modified copy of java.util.HashMap
final class pddlPropEffectDB implements Cloneable {

	public final static class Entry {
		public static final int ATMask = 0x00000800;
		public static final int AFMask = 0x00000400;
		public static final int STMask = 0x00000080;
		public static final int SFMask = 0x00000040;
		public static final int SDMask = 0x00000020;
		public static final int SAMask = 0x00000010;
		public static final int ETMask = 0x00000008;
		public static final int EFMask = 0x00000004;
		public static final int EDMask = 0x00000002;
		public static final int EAMask = 0x00000001;

		public int key;
		public int flags;

		public Entry() {
		}

		public Entry(int key, int flags) {
			this.key = key;
			this.flags = flags;
		}

		public final boolean condition(boolean value, MathForm time) {
			if (time == MathForm.StartRef) {
				if (value)
					flags |= STMask;
				else
					flags |= SFMask;
			} else if (time == MathForm.EndRef) {
				if (value)
					flags |= ETMask;
				else
					flags |= EFMask;
			} else {
				System.err.println("Too lazy to implement delayed updates inside of actions.");
				return false;
			}
			return true;
		}
		public final boolean delete(MathForm time) {
			if (time == MathForm.StartRef) {
				flags |= SDMask;
			} else if (time == MathForm.EndRef) {
				flags |= EDMask;
			} else {
				System.err.println("Too lazy to implement delayed updates inside of actions.");
				return false;
			}
			return true;
		}
		public final boolean add(MathForm time) {
			if (time == MathForm.StartRef) {
				flags |= SAMask;
			} else if (time == MathForm.EndRef) {
				flags |= EAMask;
			} else {
				System.err.println("Too lazy to implement delayed updates inside of actions.");
				return false;
			}
			return true;
		}
		public final boolean lock(boolean value) {
			if (value)
				flags |= ATMask;
			else
				flags |= AFMask;
			return true;
		}

		public final boolean make(SymbolTable<ArrayList<Integer>,Proposition> propositions, ArrayList<Update> startPoint, ArrayList<Update> firstInterval, ArrayList<Update> endPoint) {
			final int startEffects = 0x00000030;
			final int endEffects = 0x00000003;
			final int endConditions = 0x0000000C; // don't require true and false
			final int startConditions = 0x000000C0; // don't require true and false
			final int allConditions = 0x00000C00; // don't require true and false
			final int test1 = 0x00000820; // don't delete and then require true
			final int test2 = 0x00000410; // don't add and then require false
			final int test3 = 0x00000804; // don't require true then require false
			final int test4 = 0x00000408; // don't require false then require true
			final int allSubsumeTrue = 0x00000808; // end condition is redundant
			final int allSubsumeFalse = 0x00000404; // end condition is redundant

			if ((flags & endConditions) == endConditions)
				return false;
			if ((flags & startConditions) == startConditions)
				return false;
			if ((flags & allConditions) == allConditions)
				return false;
			if ((flags & test1) == test1)
				return false;
			if ((flags & test2) == test2)
				return false;
			if ((flags & test3) == test3)
				return false;
			if ((flags & test4) == test4)
				return false;
			if ((flags & allSubsumeTrue) == allSubsumeTrue)
				flags &= ~ETMask;
			if ((flags & allSubsumeFalse) == allSubsumeFalse)
				flags &= ~EFMask;

			boolean startWrite=false;
			boolean startWriteValue=false;
			boolean startRead=false;
			boolean startReadValue=false;
			boolean endWrite=false;
			boolean endWriteValue=false;
			boolean endRead=false;
			boolean endReadValue=false;
			boolean allRead=false;
			boolean allReadValue=false;

			int t;

			t = flags & allConditions;
			if (t != 0) {
				allRead = true;
				if ((t & ATMask) != 0)
					allReadValue = true;
			}
			t = flags & startConditions;
			if (t != 0) {
				startRead = true;
				if ((t & STMask)!=0)
					startReadValue = true;
			}
			t = flags & startEffects;
			if (t != 0) {
				startWrite = true;
				if ((t & SAMask)!=0)
					startWriteValue = true;
			}
			t = flags & endConditions;
			if (t != 0) {
				endRead = true;
				if ((t & ETMask)!=0)
					endReadValue = true;
			}
			t = flags & endEffects;
			if (t != 0) {
				endWrite = true;
				if ((t & EAMask)!=0)
					endWriteValue = true;
			}

			/*
			 AC SC SW EC EW
			 00000 - null
			 00001 - end change
			 00010 - end condition
			 00011 - end condition change
			 00100 - start change
			 00101 - start change + end change
			 00110 - start change + end condition
			 00111 - start change + end condition change
			 01000 - start condition
			 01001 - start condition + end change
			 01010 - start condition + end condition
			 01011 - start condition + end condition change
			 01100 - start condition change
			 01101 - start condition change + end change
			 01110 - start condition change + end condition
			 01111 - start condition change + end condition change
			 10000 - all condition
			 10001 - all to assign
			 10010 - all (with subsumed, matching [end] condition)
			 10011 - all to assign (subsumed, matching condition)
			 10100 - durative assign (subsumed, matching condition)
			 10101 - durative assign to assign
			 10110 - durative assign (with subsumed, matching [end + all] condition)
			 10111 - durative assign to assign (with subsumed, matching, [end + all] condition)
			 11000 - all (with subsumed, matching [start] condition)
			 11001 - all to assign (with subsumed, matching [start] condition)
			 11010 - all (with subsumed, matching, [start + end] condition)
			 11011 - all to assign (with subsumed, matching, [start + end] condition)
			 11100 - condition durative assign (+sub., match., all condition)
			 11101 - condition durative assign to assign (+sma condition)
			 11110 - condition durative assign (+sma condition and subsumed, matching, end condition)
			 11111 - condition durative assign to assign (+sma and sme condition)
			 */
			/*
			   101*1 - durative assign to assign (lend/use)
			   111*1 - condition durative start assign to end assign (lend/use + start condition)
			   	if TT or FF, then end assignment is redundant.  so TF (lend) or FT (use)
			   101*0 - durative start assign [consume]
			   111*0 - condition durative start assign [consume]
			   1*0*1 - condition durative end assign [produce]
			   1*0*0 - condition
			   0**** - two parts
			   001__ - start change
			   010__ - start condition
			   011__ - start condition change
			   0__01 - end change
			   0__10 - end condition
			   0__11 - end condition change
			 */

			if (allRead) {
				if (startWrite) {
					if (endWrite && startWriteValue != endWriteValue) {
						if (startRead) {
							//tca
							firstInterval.add(new TemporaryConditionAssign(propositions.get(key),key,startReadValue,startWriteValue));
						} else {
							//ta
							firstInterval.add(new TemporaryAssign(propositions.get(key),key,startWriteValue));
						}
					} else {
						if (startRead) {
							//csa
							firstInterval.add(new ConditionStartAssign(propositions.get(key),key,startReadValue,startWriteValue));
						} else {
							//a
							firstInterval.add(new Assign(propositions.get(key),key,startWriteValue));
						}
					}
				} else if (endWrite) {
					//cea
					firstInterval.add(new ConditionEndAssign(propositions.get(key),key,allReadValue,endWriteValue));
				} else {
					// c
					firstInterval.add(new Condition(propositions.get(key),key,allReadValue));
				}
			} else {
				//start
				if (startWrite) {
					if (startRead) {
						// c ch
						startPoint.add(new ConditionStartAssign(propositions.get(key),key,startReadValue,startWriteValue));
					} else {
						// ch
						startPoint.add(new Assign(propositions.get(key),key,startWriteValue));
					}
				} else if (startRead) {
					// c 
					startPoint.add(new Condition(propositions.get(key),key,startReadValue));
				}

				//end
				if (endWrite) {
					if (endRead) {
						// c ch
						endPoint.add(new ConditionStartAssign(propositions.get(key),key,endReadValue,endWriteValue));
					} else {
						// ch
						endPoint.add(new Assign(propositions.get(key),key,endWriteValue));
					}
				} else if (endRead) {
					// c 
					endPoint.add(new Condition(propositions.get(key),key,endReadValue));
				}

			}


			return true;
		}

		public boolean getValue() {
			if (flags == 0)
				return false;
			return true;
		}

	}

	/**
	 * The default initial capacity - MUST be a power of two.
	 */
	static final int DEFAULT_INITIAL_CAPACITY = 64;

	/**
	 * The table, resized as necessary. Length MUST Always be a power of two.
	 */
	Entry[] table;

	/**
	 * The number of key-value mappings contained in this map.
	 */
	public int size;

	public pddlPropEffectDB() {
		table = new Entry[DEFAULT_INITIAL_CAPACITY];
		size = 0;
	}

	public pddlPropEffectDB(int initialCapacity) {
		if (initialCapacity <= 0)
			throw new IllegalArgumentException("Illegal initial capacity: "
					+ initialCapacity);

		int capacity = Integer.highestOneBit(initialCapacity);
		if (capacity < initialCapacity)
			capacity <<= 1;

		table = new Entry[capacity];
		size = 0;
	}

	public pddlPropEffectDB(pddlPropEffectDB m) {
		int s = m.size;
		int capacity = Integer.highestOneBit(s);
		if (capacity < s)
			capacity <<= 1;
		Entry[] tbl = new Entry[capacity];
		table = tbl;
		size = 0;
		Entry[] src = m.table;
		putAllInternal(tbl, capacity, src, src.length);
	}

	// internal utilities

	@Override
	public int hashCode() {
		int result = 1;
		Entry[] tbl = table;
		int l = tbl.length;
		int i;
		Entry e;
		for(i=0;i<l;++i) {
			e = tbl[i];
			if (e == null)
				continue;
			result = 31 * result + e.key;
		}
		return result;
	}

	/**
	 * Applies a supplemental hash function to a given hashCode, which defends
	 * against poor quality hash functions. This is critical because HashMap
	 * uses power-of-two length hash tables, that otherwise encounter collisions
	 * for hashCodes that do not differ in lower bits. Note: Null keys always
	 * map to hash 0, thus index 0.
	 */
	static final int hash(int h) {
		h ^= (h >>> 20) ^ (h >>> 12);
		return h ^ (h >>> 7) ^ (h >>> 4);
	}

	public int size() {
		return size;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	Entry getInternal(int key) {
		int hash = hash(key);
		Entry[] tbl = table;
		int l = tbl.length;
		int index = hash & (l - 1);
		int i = index;
		Entry entry = tbl[i];
		if (entry == null)
			return null;
		if (entry.key == key)
			return entry;
		for (i = index + 1; i < l; ++i) {
			entry = tbl[i];
			if (entry == null)
				return null;
			if (entry.key == key)
				return entry;
		}
		for (i = 0; i < index; ++i) {
			entry = tbl[i];
			if (entry == null)
				return null;
			if (entry.key == key)
				return entry;
		}
		return null;
	}

	public Entry get(int key) {
		int hash = hash(key);
		Entry[] tbl = table;
		int l = tbl.length;
		int index = hash & (l - 1);
		int i = index;
		Entry entry = tbl[i];
		if (entry == null)
			return tbl[i] = new Entry(key, 0);
		if (entry.key == key)
			return entry;
		for (i = index + 1; i < l; ++i) {
			entry = tbl[i];
			if (entry == null)
				return tbl[i] = new Entry(key, 0);
			if (entry.key == key)
				return entry;
		}
		for (i = 0; i < index; ++i) {
			entry = tbl[i];
			if (entry == null)
				return tbl[i] = new Entry(key, 0);
			if (entry.key == key)
				return entry;
		}
		grow();
		return get(key);
	}

	/**
	 * 
	 * flags needs to be consistent with setting key to true
	 * 
	 * @param key
	 * @param flags
	 * @return Returns the new/modified Entry
	 */
	public Entry setTrue(int key, int flags) {
		int hash = hash(key);
		Entry[] tbl = table;
		int l = tbl.length;
		int t = l >> 1 + l >> 2;
		if (size > t)
			l = grow();
		int index = hash & (l - 1);
		Entry entry = tbl[index];
		if (entry == null) {
			tbl[index] = entry = new Entry(key, flags);
			++size;
			return entry;
		}
		if (entry.key == key) {
			entry.flags = flags;
			return entry;
		}
		int i;
		for (i = index + 1; i < l; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				++size;
				return entry;
			}
			if (entry.key == key) {
				if (!entry.getValue())
					++size;
				entry.flags = flags;
				return entry;
			}
		}
		for (i = 0; i < index; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				++size;
				return entry;
			}
			if (entry.key == key) {
				if (!entry.getValue())
					++size;
				entry.flags = flags;
				return entry;
			}
		}

		l = grow();
		return setTrueInternal(key, flags, tbl, l);
	}

	Entry setTrueInternal(int key, int flags, Entry[] tbl, int l) {
		int hash = hash(key);
		int index = hash & (l - 1);
		Entry entry = tbl[index];
		if (entry == null) {
			tbl[index] = entry = new Entry(key, flags);
			++size;
			return entry;
		}
		if (entry.key == key) {
			if (!entry.getValue())
				++size;
			entry.flags = flags;
			return entry;
		}
		int i;
		for (i = index + 1; i < l; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				++size;
				return entry;
			}
			if (entry.key == key) {
				if (!entry.getValue())
					++size;
				entry.flags = flags;
				return entry;
			}
		}
		for (i = 0; i < index; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				++size;
				return entry;
			}
			if (entry.key == key) {
				if (!entry.getValue())
					++size;
				entry.flags = flags;
				return entry;
			}
		}

		l = grow();
		return setTrueInternal(key, flags, tbl, l);
	}

	Entry setFalseInternal(int key, int flags, Entry[] tbl, int l) {
		int hash = hash(key);
		int index = hash & (l - 1);
		Entry entry = tbl[index];
		if (entry == null) {
			tbl[index] = entry = new Entry(key, flags);
			return entry;
		}
		if (entry.key == key) {
			if (entry.getValue())
				--size;
			entry.flags = flags;
			return entry;
		}
		int i;
		for (i = index + 1; i < l; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				return entry;
			}
			if (entry.key == key) {
				if (entry.getValue())
					--size;
				entry.flags = flags;
				return entry;
			}
		}
		for (i = 0; i < index; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				return entry;
			}
			if (entry.key == key) {
				if (entry.getValue())
					--size;
				entry.flags = flags;
				return entry;
			}
		}

		l = grow();
		return setFalseInternal(key, flags, tbl, l);
	}

	void putAllInternal(Entry[] tbl, int l, Entry[] src, int s) {
		for (int i = 0; i < s; ++i) {
			Entry e = src[i];
			if (e != null) {
				int v = e.flags;
				if (e.getValue())
					setTrueInternal(e.key, v, tbl, l);
				else {
					if (v != 0)
						setFalseInternal(e.key, v, tbl, l);
				}
			}
		}
	}

	int grow() {
		Entry[] oldTable = table;
		int l = oldTable.length;
		int s = l << 1;
		Entry[] newTable = new Entry[s];
		table = newTable;
		size = 0;
		putAllInternal(newTable, s, oldTable, l);
		return s;
	}

	void grow(int s) {
		Entry[] oldTable = table;
		int l = oldTable.length;
		Entry[] newTable = new Entry[s];
		table = newTable;
		size = 0;
		putAllInternal(newTable, s, oldTable, l);
	}

	public void putAll(pddlPropEffectDB m) {
		int l = m.size;
		if (l == 0)
			return;
		Entry[] tbl = table;
		int s = tbl.length;
		if (l > s) {
			s = Integer.highestOneBit(l);
			if (s < l)
				s <<= 1;
			grow(s);
			tbl = table;
		}

		Entry[] src = m.table;
		int sz = src.length;
		putAllInternal(tbl, s, src, sz);
	}

	/**
	 * @param key
	 *          proposition id to be set to false
	 * @param flags
	 * 			needs to be consistent with setting to false
	 * 
	 */
	public Entry setFalse(int key, int flags) {
		int hash = hash(key);
		Entry[] tbl = table;
		int l = tbl.length;
		int index = hash & (l - 1);
		Entry entry = tbl[index];
		if (entry == null) {
			tbl[index] = entry = new Entry(key, flags);
			return entry;
		}
		if (entry.key == key) {
			if (entry.getValue())
				--size;
			entry.flags = flags;
			return entry;
		}
		int i;
		for (i = index + 1; i < l; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				return entry;
			}
			if (entry.key == key) {
				if (entry.getValue())
					--size;
				entry.flags = flags;
				return entry;
			}
		}
		for (i = 0; i < index; ++i) {
			entry = tbl[i];
			if (entry == null) {
				tbl[index] = entry = new Entry(key, flags);
				return entry;
			}
			if (entry.key == key) {
				if (entry.getValue())
					--size;
				entry.flags = flags;
				return entry;
			}
		}

		l = grow();
		return setFalseInternal(key, flags, tbl, l);
	}

	/**
	 * Doesn't _really_ clear
	 */
	public void clear() {
		Entry[] tbl = table;
		int l = tbl.length;
		for (int i = 0; i < l; ++i) {
			Entry e = tbl[i];
			if (e != null)
				e.flags = 0;
		}
		size = 0;
	}

	/**
	 * @return Deep copy
	 */
	public pddlPropEffectDB clone() {
		Entry[] tbl = table;
		int l = tbl.length;
		pddlPropEffectDB result = new pddlPropEffectDB(l);
		result.putAllInternal(result.table, l, tbl, l);

		return result;
	}

	// only valid if there is nothing to advance to, i.e., everything is
	// unlocked
	public final boolean equals(pddlPropEffectDB q) {
		Entry[] tbl = table;
		int s = table.length;
		if (size != q.size)
			return false;
		int i;
		Entry a, b;
		for (i = 0; i < s; ++i) {
			a = tbl[i];
			if (a == null || !a.getValue()) 
				continue;
			b = q.getInternal(a.key);
			if (b == null || !b.getValue())
				return false;
		}
		return true;
	}

	public final void lock(int key, boolean value) {
		Entry e = get(key);
		if (!e.getValue()) {
			if (e.lock(value))
				++size;
		} else
			e.lock(value);
	}
	public final void condition(int key, boolean value, MathForm time) {
		Entry e = get(key);
		if (!e.getValue()) {
			if (e.condition(value,time))
				++size;
		} else
			e.condition(value,time);
	}
	public final void add(int key, MathForm time) {
		Entry e = get(key);
		if (!e.getValue()) {
			if (e.add(time))
				++size;
		} else
			e.add(time);
	}
	public final void delete(int key, MathForm time) {
		Entry e = get(key);
		if (!e.getValue()) {
			if (e.delete(time))
				++size;
		} else
			e.delete(time);
	}

	public boolean makeAll(SymbolTable<ArrayList<Integer>,Proposition> propositions, ArrayList<Update> startPoint, ArrayList<Update> allInterval, ArrayList<Update> endPoint) {
		boolean possible=true;
		for (int i=0; i < this.table.length; ++i) {
			Entry e = table[i];
			if (e != null && e.getValue()) {
				possible &= e.make(propositions,startPoint,allInterval,endPoint);
			}
		}
		return possible;
	}

}





